/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation;


import static br.ufpe.cin.imlm2.jbeat.soot.common.CommonExceptions.getARITHMETIC;
import static br.ufpe.cin.imlm2.jbeat.soot.common.CommonExceptions.getARRAYINDEXOUTOFBOUNDS;
import static br.ufpe.cin.imlm2.jbeat.soot.common.CommonExceptions.getCLASSCAST;
import static br.ufpe.cin.imlm2.jbeat.soot.common.CommonExceptions.getNEGATIVEARRAYSIZE;
import static br.ufpe.cin.imlm2.jbeat.soot.common.CommonExceptions.getNULLPOINTER;

import java.util.List;
import java.util.Set;

import soot.Body;
import soot.Immediate;
import soot.SootClass;
import soot.Value;
import soot.jimple.AddExpr;
import soot.jimple.AndExpr;
import soot.jimple.ArrayRef;
import soot.jimple.AssignStmt;
import soot.jimple.BinopExpr;
import soot.jimple.BreakpointStmt;
import soot.jimple.CastExpr;
import soot.jimple.CaughtExceptionRef;
import soot.jimple.CmpExpr;
import soot.jimple.CmpgExpr;
import soot.jimple.CmplExpr;
import soot.jimple.DivExpr;
import soot.jimple.EnterMonitorStmt;
import soot.jimple.EqExpr;
import soot.jimple.ExitMonitorStmt;
import soot.jimple.Expr;
import soot.jimple.GeExpr;
import soot.jimple.GotoStmt;
import soot.jimple.GtExpr;
import soot.jimple.IdentityStmt;
import soot.jimple.IfStmt;
import soot.jimple.InstanceFieldRef;
import soot.jimple.InstanceOfExpr;
import soot.jimple.IntConstant;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.LeExpr;
import soot.jimple.LengthExpr;
import soot.jimple.LookupSwitchStmt;
import soot.jimple.LtExpr;
import soot.jimple.MulExpr;
import soot.jimple.NeExpr;
import soot.jimple.NegExpr;
import soot.jimple.NewArrayExpr;
import soot.jimple.NewExpr;
import soot.jimple.NewMultiArrayExpr;
import soot.jimple.NopStmt;
import soot.jimple.OrExpr;
import soot.jimple.ParameterRef;
import soot.jimple.Ref;
import soot.jimple.RemExpr;
import soot.jimple.RetStmt;
import soot.jimple.ReturnStmt;
import soot.jimple.ReturnVoidStmt;
import soot.jimple.ShlExpr;
import soot.jimple.ShrExpr;
import soot.jimple.StaticFieldRef;
import soot.jimple.SubExpr;
import soot.jimple.TableSwitchStmt;
import soot.jimple.ThisRef;
import soot.jimple.ThrowStmt;
import soot.jimple.UshrExpr;
import soot.jimple.XorExpr;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.InstructionVisitor;

public class GeneratesVisitor extends InstructionVisitor {


	public GeneratesVisitor(Body b) {
		super(b);
	}

	@Override
	public void caseBreakpointStmt(BreakpointStmt stmt) {
		// Empty.
	}

	@Override
	public void caseInvokeStmt(InvokeStmt stmt) {
		this.cache.clear();
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseAssignStmt(AssignStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getLeftOp());
		this.investigateValue(stmt.getRightOp());
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}


	@Override
	public void caseIdentityStmt(IdentityStmt stmt) {
		// Somente um binding de nomes
	}

	@Override
	public void caseEnterMonitorStmt(EnterMonitorStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getOp());
		this.checkNullCheckTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}



	@Override
	public void caseExitMonitorStmt(ExitMonitorStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getOp());
		this.checkNullCheckTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseGotoStmt(GotoStmt stmt) {
		// Somente excecoes assincronas
	}

	@Override
	public void caseIfStmt(IfStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getCondition());
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void caseLookupSwitchStmt(LookupSwitchStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getKey());
		List<Value> cases = stmt.getLookupValues();
		for (Value value : cases) {
			this.investigateValue(value);
		}
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseNopStmt(NopStmt stmt) {	
		// Empty.
	}

	@Override
	public void caseRetStmt(RetStmt stmt) {
		// Somente excecoes assincronas  e IllegalMonitorState

	}

	@Override
	public void caseReturnStmt(ReturnStmt stmt) {
		// Somente excecoes assincronas  e IllegalMonitorState
	}

	@Override
	public void caseReturnVoidStmt(ReturnVoidStmt stmt) {
		// Somente excecoes assincronas  e IllegalMonitorState

	}

	@Override
	public void caseTableSwitchStmt(TableSwitchStmt stmt) {
		this.cache.clear();
		this.investigateValue(stmt.getKey());
		this.checkNullCheckTag(stmt);
		this.checkArrayBoundsTag(stmt);
		this.filterTrappedOnes(stmt);
		this.exceptions.addAll(this.cache);
	}

	@Override
	public void caseThrowStmt(ThrowStmt stmt) {

	}

	@Override
	public void defaultCase(Object obj) {
		throw new IllegalArgumentException("Stmt invalido");
	}


	protected void investigateValue(Value v) {
		if(v instanceof Ref){
			investigate((Ref)v);
		} else if(v instanceof Expr){
			investigate((Expr)v);
		} else if(v instanceof Immediate){
			return;
		} else {
			throw new IllegalArgumentException("Value invalido");			
		}
	}

	@SuppressWarnings("unchecked")
	private void investigate(Expr v) {
		if(v instanceof AddExpr){
			investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof AndExpr){
			investigateBinopExpr((BinopExpr)v);
		} else if(v instanceof CastExpr){
			this.cache.add(getCLASSCAST());
			investigateValue(((CastExpr) v).getOp());
		} else if(v instanceof CmpExpr){
			investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof CmpgExpr){
			investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof CmplExpr){
			investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof DivExpr){
			Value divisor = ((DivExpr) v).getOp2();
			if(divisor instanceof IntConstant){
				int div = ((IntConstant)divisor).value;
				if(div == 0) this.cache.add(getARITHMETIC());
			} else {
				this.cache.add(getARITHMETIC());				
			}
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof EqExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof GeExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof GtExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof InstanceOfExpr){
			this.investigateValue(((InstanceOfExpr) v).getOp());
		} else if(v instanceof LeExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof LengthExpr){
			this.investigateValue(((LengthExpr) v).getOp());
		} else if(v instanceof LtExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof MulExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof NeExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof NegExpr){
			this.investigateValue(((NegExpr) v).getOp());
		} else if(v instanceof NewArrayExpr){
			Value size = ((NewArrayExpr) v).getSize();
			if(size instanceof IntConstant){
				int val = ((IntConstant)size).value;
				if(val < 0) this.cache.add(getNEGATIVEARRAYSIZE());
			} else this.cache.add(getNEGATIVEARRAYSIZE());
			this.investigateValue(size);
		} else if(v instanceof NewExpr){
			return;
		} else if(v instanceof NewMultiArrayExpr){
			List<Value> sizes = ((NewMultiArrayExpr) v).getSizes();
			boolean maybeNegative = false;
			for (Value size : sizes ) {
				if(size instanceof IntConstant){
					int val = ((IntConstant)size).value;
					if(val < 0) maybeNegative = true;
				} else maybeNegative = true;
				this.investigateValue(size);
			}
			if(maybeNegative) this.cache.add(getNEGATIVEARRAYSIZE());
		} else if(v instanceof OrExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof RemExpr){
			// TODO RemExpr?
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof ShlExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof ShrExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof SubExpr){
			this.investigateBinopExpr((BinopExpr) v);
		} else if(v instanceof UshrExpr){
			this.investigateBinopExpr((BinopExpr) v);
		}  else if(v instanceof XorExpr){
			this.investigateBinopExpr((BinopExpr) v);			
		} else if(v instanceof InvokeExpr){
			return;
		} else {
			throw new IllegalArgumentException("Expressao Invalida " + v);
		}

	}

	private void investigate(Ref v) {
		Set<SootClass> throwables = this.cache;
		if(v instanceof ArrayRef){
			throwables.add(getNULLPOINTER());
			throwables.add(getARRAYINDEXOUTOFBOUNDS());
			this.investigateValue(((ArrayRef) v).getBase());
			this.investigateValue(((ArrayRef) v).getIndex());
		} else if(v instanceof CaughtExceptionRef){
			return ;
		} else if(v instanceof InstanceFieldRef){
			throwables.add(getNULLPOINTER());
			this.investigateValue(((InstanceFieldRef) v).getBase());
		} else if(v instanceof ParameterRef){
			return;
		} else if(v instanceof StaticFieldRef){
			return ;
		} else if(v instanceof ThisRef){
			return ;
		} else {	
			throw new IllegalArgumentException("Ref invalido - " + v);
		}

	}

	private void investigateBinopExpr(BinopExpr v) {
		investigateValue((v).getOp1());
		investigateValue((v).getOp2());
	}

}
